package com.zzyl.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.zzyl.base.PageResponse;
import com.zzyl.config.WebSocketServer;
import com.zzyl.constant.CacheConstants;
import com.zzyl.dto.AlertNotifyDto;
import com.zzyl.dto.AlertRuleDto;
import com.zzyl.entity.AlertData;
import com.zzyl.entity.AlertRule;
import com.zzyl.mapper.AlertDataMapper;
import com.zzyl.mapper.AlertRuleMapper;
import com.zzyl.mapper.DeviceMapper;
import com.zzyl.mapper.UserRoleMapper;
import com.zzyl.service.AlertRuleService;
import com.zzyl.vo.AlertRuleVo;
import com.zzyl.vo.DeviceDataVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author sjqn
 */
@Slf4j
@Service
public class AlertRuleServiceImpl implements AlertRuleService {

    @Autowired
    private AlertRuleMapper alertRuleMapper;

    /**
     * 新增报警规则
     *
     * @param alertRuleDto 报警规则请求模型
     */
    @Override
    public void createRule(AlertRuleDto alertRuleDto) {
        AlertRule alertRule = BeanUtil.toBean(alertRuleDto, AlertRule.class);
        alertRuleMapper.insert(alertRule);
    }

    /**
     * 分页条件查询
     *
     * @param pageNum       页码
     * @param pageSize      页面大小
     * @param alertRuleName 报警规则名称
     * @param productKey    产品key
     * @param functionName  功能名称
     * @return 分页结果
     */
    @Override
    public PageResponse<AlertRuleVo> getAlertRulePage(Integer pageNum, Integer pageSize, String alertRuleName, String productKey, String functionName) {
        PageHelper.startPage(pageNum, pageSize);
        Page<AlertRuleVo> page = alertRuleMapper.page(alertRuleName, productKey, functionName);
        //添加提示
        page.getResult().forEach(alertRuleVo -> {
            String alertReason = CharSequenceUtil.format("{}{}{},持续{}个周期就报警", alertRuleVo.getFunctionName(), alertRuleVo.getOperator(), alertRuleVo.getValue(), alertRuleVo.getDuration());
            alertRuleVo.setRules(alertReason);
        });

        return PageResponse.of(page, AlertRuleVo.class);
    }

    /**
     * 删除报警
     *
     * @param id 报警规则id
     */
    @Override
    public void deleteAlertRule(Long id) {
        alertRuleMapper.deleteByPrimaryKey(id);
    }

    /**
     * 修改报警
     *
     * @param id           报警规则id
     * @param alertRuleDto 报警规则更新请求模型
     */
    @Override
    public void updateAlertRule(Long id, AlertRuleDto alertRuleDto) {
        AlertRule alertRule = BeanUtil.toBean(alertRuleDto, AlertRule.class);
        alertRuleMapper.updateByPrimaryKeySelective(alertRule);
    }

    /**
     * 查询报警信息
     *
     * @param id 报警规则id
     * @return 报警信息
     */
    @Override
    public AlertRuleVo readAlertRule(Long id) {
        AlertRule alertRule = alertRuleMapper.selectByPrimaryKey(id);
        return BeanUtil.toBean(alertRule, AlertRuleVo.class);
    }

    /**
     * 启用或禁用
     *
     * @param id     报警id
     * @param status 报警规则状态
     */
    @Override
    public void enableOrDisable(Long id, Integer status) {
        alertRuleMapper.updateStatus(id, status);
    }

    /**
     * 过滤数据
     *
     * @param deviceDataVo
     */
    @Override
    public void alertFilter(DeviceDataVo deviceDataVo) {
        // 判断上报的数据，是否是一分钟之内的数据
        // 获取上报数据的上报时间
        LocalDateTime alarmTime = deviceDataVo.getAlarmTime();
        // 计算上报时间与当前时间的时间间隔
        long between = LocalDateTimeUtil.between(alarmTime, LocalDateTime.now(), ChronoUnit.MINUTES);
        // 判断间隔时间，是否超过1分钟
        if (between > 1) {
            // 结束方法
            return;
        }
        // 获取该产品该物模型的所有设备的所有报警规则
        List<AlertRule> allAlertRules = alertRuleMapper.selectByFunctionId(deviceDataVo.getFunctionId(), "-1", deviceDataVo.getProductKey());
        // 获取该产品指定设备的该物模型的报警规则
        List<AlertRule> thisAlertRules = alertRuleMapper.selectByFunctionId(deviceDataVo.getFunctionId(), deviceDataVo.getIotId(), deviceDataVo.getProductKey());
        // 准备一个新集合，用于合并规则数据
        List<AlertRule> allRules = new ArrayList<>();
        // 判断allAlertRules是否为空
        if (allAlertRules != null || thisAlertRules != null) {
            // 合并规则
            allRules.addAll(allAlertRules);
            allRules.addAll(thisAlertRules);
        }
        // 判断合并后的集合，是否有数据
        if (allRules.isEmpty()) {
            return;
        }
        // 遍历合并后的规则，判断是否满足报警规则deviceDateAlarmHandler()
        allRules.forEach(alertRule -> {
            deviceDateAlarmHandler(deviceDataVo, alertRule);
        });
    }

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Value("${zzyl.alert.deviceMaintainerRole}")
    private String deviceMaintainerRole;

    @Value("${zzyl.alert.managerRole}")
    private String managerRole;


    /**
     * 过滤数据是否触发报警规则
     *
     * @param deviceDataVo
     * @param rule
     */
    private void deviceDateAlarmHandler(DeviceDataVo deviceDataVo, AlertRule rule) {
        // 解析规则生效时间数据
        String[] arr = rule.getAlertEffectivePeriod().split("~");
        LocalTime startTime = LocalTime.parse(arr[0]);
        LocalTime endTime = LocalTime.parse(arr[1]);
        // 获取设备上报数据时间
        LocalTime alarmTime = deviceDataVo.getAlarmTime().toLocalTime();
        // 判断 设备上报数据时间，是否在规则生效时间范围内
        if (startTime.isAfter(alarmTime) || endTime.isBefore(alarmTime)) {
            // 不在生效范围内，直接结束校验
            return;
        }
        // 准备报警次数的redis - key    前缀 + iotid + functionId + 规则id
        String triggerKey = CacheConstants.ALERT_TRIGGER_COUNT_PREFIX + deviceDataVo.getIotId()
                + deviceDataVo.getFunctionId() + rule.getId();
        // 判断 上报数据，是否达到报警阈值
        // 获取报警阈值
        Float value = rule.getValue();
        // 获取上报数据值
        Float dataValue = Float.valueOf(deviceDataVo.getDataValue());
        // 比较两个数的大小
        int num = dataValue.compareTo(value);
        // 获取运算符 判断
        if (!((rule.getOperator().equals(">=") && num >= 0) ||
                (rule.getOperator().equals("<") && num < 0))) {
            // 删除redis中的报警数
            redisTemplate.delete(triggerKey);
            return;
        }
        // 准备沉默周期的redis - key
        String silentKey = CacheConstants.ALERT_SILENT_PREFIX + deviceDataVo.getIotId()
                + deviceDataVo.getFunctionId() + rule.getId();
        String silent = redisTemplate.opsForValue().get(silentKey);
        // 判断 redis中是否有沉默周期数据
        if (silent != null) {
            // 在上次报警数据成功后，的沉默周期时间内，再次报警无效。
            return;
        }
        // 从redis中获取报警次数的值
        String trigger = redisTemplate.opsForValue().get(triggerKey);
        // 判断 报警次数 是否有值
        int alertCount = trigger == null ? 1 : Integer.parseInt(trigger) + 1;
        // 判断报警次数，是否与持续周期相同
        if (alertCount != rule.getDuration()) {
            // 不想等，表示本次上报，没有达到报警要求(持续周期次数)
            redisTemplate.opsForValue().set(triggerKey, alertCount + "");
            return;
        }
        // 达到的报警的次数，上报报警数据
        // 删除redis中的报警次数
        redisTemplate.delete(triggerKey);
        // 在redis中，添加沉默周期数据，同时指定过期时间
        // redis的过期时间应该与沉默周期相同。
        redisTemplate.opsForValue().set(silentKey, "silent:" + rule.getDuration() + "分钟", rule.getAlertSilentPeriod(), TimeUnit.MINUTES);

        // 判断 上报设备是否是老人数据
        // 本次上报的是老人的设备数据
        List<Long> userIds = new ArrayList<>();  // 用来装护理员们或修理工们id
        if (rule.getAlertDataType() == 0) {
            // 判断是否是老人的随声设备(智能手表-老人)还是固定设备(睡眠检测带-床上)
            if (deviceDataVo.getLocationType() == 0) {  // 随声设备   -- 找老人对应的护理员
                userIds = deviceMapper.selectNursingIdsByIotIdWithElder(deviceDataVo.getIotId());
            } else {
                userIds = deviceMapper.selectNursingIdsByIotIdWithBed(deviceDataVo.getIotId());
            }
        } else {
            // 如果是固定设备，则查询维修工们的id
            userIds = userRoleMapper.selectUserIdByRoleName(deviceMaintainerRole);
        }
        // 查询管理员id
        List<Long> adminIds = userRoleMapper.selectUserIdByRoleName(managerRole);
        // 合并管理员id集合 和 装护理员们或修理工们id集合
        adminIds.addAll(userIds);
        // 去重
        List<Long> ids = adminIds.stream().distinct().collect(Collectors.toList());
        // 批量插入报警数据
        insertAlertData(ids,rule,deviceDataVo);
        // 发送报警消息
        // 创建一个报警提醒对象 AlertNotifyDto
        AlertNotifyDto bean = BeanUtil.toBean(deviceDataVo, AlertNotifyDto.class);
        bean.setFunctionName(rule.getFunctionName());
        bean.setAlertDataType(rule.getAlertDataType());
        bean.setNotifyType(1); // 1: 报警  0：接触报警
        webSocketServer.sendMessageToConsumer(bean,ids);
    }
    @Autowired
    private WebSocketServer webSocketServer;

    @Autowired
    private AlertDataMapper alertDataMapper;

    /**
     * 保存报警数据
     *
     * @param allIds
     * @param rule
     * @param deviceDataVo
     */
    private void insertAlertData(List<Long> allIds, AlertRule rule, DeviceDataVo deviceDataVo) {
        // 先拷贝属性
        AlertData data = BeanUtil.toBean(rule, AlertData.class);
        // 创建AlertData对象，将其他数据，封装到该对象中。
        data.setType(rule.getAlertDataType());
        data.setAlertRuleId(rule.id);
        data.setStatus(0); // 0 待处理
        String alertReason = CharSequenceUtil.format("{}{}{},持续{}个周期就报警", rule.getFunctionName(),
                rule.getOperator(), rule.getValue(), rule.getDuration());
        data.setAlertReason(alertReason);
        data.setAccessLocation(deviceDataVo.getAccessLocation());
        data.setLocationType(deviceDataVo.getLocationType());
        data.setPhysicalLocationType(deviceDataVo.getPhysicalLocationType());
        data.setDataValue(deviceDataVo.getDataValue());
        data.setNickname(deviceDataVo.getNickname());

        // 遍历ids集合，将数据，封装到AlertData对象中
        List<AlertData> collect = allIds.stream().map(id -> {
            AlertData alertData = BeanUtil.toBean(data, AlertData.class);
            alertData.setUserId(id); // 封装用户(护理员、维修工、超集管理员)id
            return alertData;
        }).collect(Collectors.toList());

        // 将集合中的数据，批量插入到报警数据表中
        alertDataMapper.batchInsert(collect);
    }
}
